VERSION="$$VERSION$$"

SH_INCLUDE_PATH="$SH_INCLUDE_PATH:`dirname $0`:`dirname $0`/bin:`dirname $0`/lib:.:./bin:./lib:$HOME/bin:$HOME/lib:$$SEARCHPATH$$"

[ -z "$PAGER" ] && PAGER=less

# =============================================================================
# INTERNAL FUNCTIONS
# =============================================================================

# a RNG for testing
# very basic
include_random () {
    local r quiet
    [ "x$1" = "x-q" ] && { quiet=true; shift ; } || quiet=false
    [ -n "$1" ] && denominator=$1 || denominator=2
    [ -c /dev/urandom ] && dev="/dev/urandom" || dev="/dev/random"
    r=$(( `hexdump -n1 -e\"%u\" $dev` % $denominator ))
    $quiet || echo $r
    return $r
}
    
include_rm_preprocessor_comment () {
    echo "$*" | sed -r 's/[^#]*#(if|elif)//I'
}

# remove conditional comment or preproccesing block definition
include_rm_comment () {
    echo "$*" | sed -r 's/[^#]*#//I'
}

include_mktemp () {
    [ -n "$TMPDIR" ] && tdir=$TMPDIR || tdir='/tmp'
    if ! [ -w "$tdir" ]; then
        echo "tmpdir '$tdir' not writeble";
        exit 1
    fi 
    tmpfile=`mktemp XXXXXX`
    echo $tmpfile
}


# preprocesses the given file
#
# - find preprocessing blocks, execute them and write output to outfile
# - find conditional comments, check them and write block into outfile if true
include_preprocess () {
    local file outfile in_condition output tmpfile preprocess_block
    file=$1
    outfile=$2
    in_condition=false
    output=true
    tmpfile=""
    preprocess_block=false
    cat $file |\
    while read line; do
        case "$line" in
            *#preprocess_start*|*#PREPROCESS_START*)
                # start of preproccesing block
                preprocess_block=true
                tmpfile=`include_mktemp XXXXXX`
                continue
                ;;
            *#preprocess_end*|*#PREPROCESS_END*)
                # end of preproccesing block
                preprocess_block=false
                sh $tmpfile >> $outfile
                rm $tmpfile
                continue
                ;;
            *#if*|*#IF*)
                # condittinal comment
                # check it and turn output on if true
                if ( eval `include_rm_preprocessor_comment "$line"` 2>/dev/null >/dev/null); then
                    in_condition=true
                    output=true
                else
                    in_condition=false
                    output=false
                fi
                continue
                ;;
            *#elif*|*#ELIF*)
                # condittinal comment
                # check it and turn output on if true
                if ! $in_condition && ( eval `include_rm_preprocessor_comment "$line"` 2>/dev/null >/dev/null); then
                    in_condition=true
                    output=true
                else
                    output=false
                fi
                continue
                ;;
            *#else*|*#ELSE*)
                # condittinal comment
                # turn output on if no other condtion was accepted
                if ! $in_condition; then 
                    in_condition=true
                    output=true
                else
                    in_condition=false
                    output=false
                fi
                continue
                ;;
            *#fi*|*#FI*)
                # exit conditional comment block
                in_condition=false
                output=true
                continue
                ;;
            *)
                # if nothing of above run output
                if $preprocess_block; then
                    include_rm_comment "$line" >> $tmpfile
                elif $output; then
                    if $in_condition; then 
                        include_rm_comment "$line" 2>/dev/null >> $outfile
                    else
                        echo "$line" 2>/dev/null >> $outfile
                    fi
                fi
                ;;
        esac
    done
}


# get absolute path of given file
include_get_abs_path () {
    case $1 in
        /*) echo $1;;
        *) echo $PWD/$1;;
    esac
}


# get all defined variables
include_get_set_vars () {
    (set -o posix 2>/dev/null) && set -o posix
    set | /usr/bin/env cut -f 1 -d '=' | /usr/bin/env sed -r "s/([^[:alnum:]_]|OPTIND)//g"
}

# get all variables that are defined in module
include_check_for_variables () {
    (
        local file
        file=`include_get_abs_path $1`
        unset `include_get_set_vars | sed -e "s/file//"` 2>/dev/null
        . $file >/dev/null
        unset file 2>/dev/null
        include_get_set_vars
    )
}


# check if funtion with name ($1) is defined
include_is_function_defined () {
    { type "$1" | grep "function" ; } 2>/dev/null >/dev/null
    return $?
}

# get all function named defined in module
include_check_for_functions () {
    local file funcs
    file=`include_get_abs_path $1`
    funcs=`cat $file | grep -e "\w\ \{0,\}()" | sed -e "s/\([[:alnum:]_]*\)\ \{0,\}().*/\1/"`
    (
        . $file > /dev/null
        for f in $funcs; do
            include_is_function_defined "$f" && echo "$f"
        done
    )
}

# split given strings (as parameters or by stdin) into seperate chars
include_split_into_chars () {
    if [ -n "$1" ]; then
        echo "$*" | awk 'BEGIN{FS=""};END{n=split($0, a); for (i=0; i<=n;i++) { print a[i] }}'
    else
        cat | awk 'BEGIN{FS=""};END{n=split($0, a); for (i=0; i<=n;i++) { print a[i] }}'
    fi
}

#search for a file ($1) in given path definitions
# path definitions are ':' seperated directory names
include_search_for_in () {
    local mode quiet search_for search_in found
    mode=""
    quiet=false
    if [ "x$1" = "x-m" ]; then
        mode=$2
        shift 2
    fi
    if [ "x$1" = "x-q" ]; then
        quiet=true
        shift
    fi
    search_for=$1
    shift
    search_in=''
    for d in `echo $* | sed -e "s/:/ /g"`; do
        [ -d "$d" ] && search_in="$search_in $d/"
    done

    found=`find $search_in -name $search_for 2>/dev/null | head -n 1`
    [ -z "$found" ] && return 1
    if [ -n "$mode" ]; then
        for m in `include_split_into_chars $mode`; do
            [ -$m $found ] || return 1
        done
    fi

    $quiet || echo $found
    return 0
}

# check for 'depends on' definitions
include_check_dependencies () {
    local file dep
    file=`include_get_abs_path $1`
    grep "^#[ \t]*depends[ \t]*on" $file |\
    while read line; do
        dep=`echo "$line" | sed -e "s/^#[ \t]*depends[ \t]*on[ \t]*\(.*\)/\1/"`
        include_depends_on $dep || return 1
    done
    
} 

# check if given dependecy can be met
include_depends_on () {
    local genInput inputString prog

    genInput=false
    if [ "x$1" = "x-i" ]; then
        genInput=true
        inputString="0123456789abcdefghijklmnopqrstuvwxyz"
        shift
    elif [ "x$1" = "x-I" ]; then
        genInput=true
        inputString=$2
        shift 2
    fi

    if $genInput ; then
        eval 'echo "$inputString" |' "$*" '2>/dev/null > /dev/null'
    else
        eval "$*" '2>/dev/null > /dev/null'
    fi

    if [ $? -ne 0 ]; then
        prog=`echo $* | cut -f 1 -d ' '`
        include_search_for_in -m x -q $prog $SH_INCLUDE_PATH $PATH && return 0
        echo "dependency '$*' could not be met." >&2
        return 1
    fi
    return 0
}


# substitued ' ' with '|' for regex
include_or_params () {
    echo "$@" | sed -e 's/[[:space:]]\{1,\}/|/g'
}


# MAIN include function
# here all magic is done ;-)
# 
# it will generate temp files, pars the files
# preproccess the file, chek for depends, check conditional comments
# and import the processed file
include_import () {
    local module file prefix imports tmp0 tmp1 vars funcs regex regex0
    PATH=$SH_INCLUDE_PATH:$PATH
    module=$1
    file=$2
    prefix=$3
    shift 3
    imports="$*"

    # generate temp files
    tmp0=`include_mktemp XXXXXXXX`
    tmp1=`include_mktemp XXXXXXXX`

    # setup cleanup of tmp files
    clean () {
        for f in $tmp0 $tmp1; do
            [ -f "$f" ] && rm $f
        done
    }
    trap clean EXIT


    # checks for 'depends on' comments
    include_check_dependencies $file || exit 1

    # checkfs for preprocessing and conditional comments
    include_preprocess $file $tmp0

    # get var defined in module
    vars=`include_check_for_variables $tmp0`

    # get function defined in module
    funcs=`include_check_for_functions $tmp0`

    # add prefix to vars and function that need to be imported
    [ "x$imports" = "xall" -o "x$imports" = "x" ] && imports="$vars $funcs"
    regex0=`include_or_params $imports`
    if [ -n "$regex0" ]; then
        regex='(^|[^[:alnum:]_]{1,})('$regex0')([^[:alnum:]_]{1,}|$)'
        cat $tmp0 | sed -r "s/$regex/\1${prefix}\2\3/g" > $tmp1
    else
        cp $tmp0 $tmp1
    fi

	# debugging
    [ -n "$INCLUDE_DEBUG" ] && $PAGER $tmp1

    # inlcude the processed module
    . `include_get_abs_path $tmp1`

    if [ $? -ne 0 ]; then
        echo "ERROR: Could include '$file'."
        exit 1
    fi

    # unset vars and functions we do not need
    if [ -n "$regex0" ]; then
        unset `echo $vars | sed -r "s/$regex0//g"` 2>/dev/null
        unset -f `echo $funcs | sed -r "s/$regex0//g"` 2>/dev/null
    fi

    clean
}


# basic import call
_import () {
    local module file prefix path
    PATH=$SH_INCLUDE_PATH:$PATH
    module=$1
    prefix=$2
    shift 2
    file=${module%.sh}.sh
    path=`include_search_for_in -m r $file $PATH`
    if [ $? -eq 0 ]; then
        include_import "$module" "$path" "$prefix" "$@"
    else
        echo "module file '$file' not found" >&2
        exit 1
    fi
}


# =============================================================================
# USER FUNCTIONS
# =============================================================================


include () {
    _import "$1" '' "all"
}

import () {
    local prefix
    [ "x$2" = "xas" ] && prefix=$3 || prefix=$1
    prefix=${prefix}_
    _import "$1" "$prefix" "all"
}


from () {
    local module
    module=$1
    [ "x$2" != "ximport" ] && return 1
    shift 2
    _import "$module" "" "$@"
}

